package de.rts.xml;

import java.awt.Point;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

import de.rts.components.RailComponent;
import de.rts.components.RailwayMap;
import de.rts.components.Segment;
import de.rts.components.Sensor;
import de.rts.components.SensorType;
import de.rts.components.Signal;
import de.rts.components.Train;

public class MapParser
{
  private RailwayMap                                          railwayMap;

  private static final Logger                                 logger = Logger.getLogger(MapParser.class
                                                                       .getCanonicalName());

  private Map<Integer, LinkedHashMap<Integer, List<Segment>>> switchSegmentMap;

  private LinkedHashMap<Integer, List<Segment>>               unitMap;

  public MapParser()
  {
    this.railwayMap = new RailwayMap();
    this.switchSegmentMap = new LinkedHashMap<Integer, LinkedHashMap<Integer, List<Segment>>>();
    this.unitMap = new LinkedHashMap<Integer, List<Segment>>();
  }

  @SuppressWarnings("unchecked")
  protected Object parseFile(String uri) throws Exception
  {
    SAXReader reader = new SAXReader();
    Document document = reader.read(uri);
    Element root = document.getRootElement();
    Validator.validateXML(root);
    logger.info("XML is valid");

    processNodes(document.selectNodes(RailComponent.RAILWAY_SEGMENTS), RailComponent.RAILWAY_SEGMENTS);
    processNodes(document.selectNodes(RailComponent.RAILWAY_SIGNALS), RailComponent.RAILWAY_SIGNALS);
    processNodes(document.selectNodes(RailComponent.RAILWAY_TRAINS), RailComponent.RAILWAY_TRAINS);
    logger.info("Railway map creation completed.");

    for (Train train : this.railwayMap.getTrains())
    {
      int id = Integer.parseInt(train.getTrainId());
      Map<Integer, LinkedList<RailComponent>> routeMap = this.railwayMap.getRouteMap();
      LinkedList<RailComponent> list = routeMap.get(id);
      for (RailComponent c : list)
      {
        if (c instanceof Segment) train.setSegment((Segment) c);
        if (c instanceof Sensor) train.setSensor((Sensor) c);
        if (c instanceof Signal) train.setSignal((Signal) c);
      }
      train.setSwitchSegment(id, this.switchSegmentMap.get(id));
    }
    return this.railwayMap;
  }

  private void processNodes(List<Node> selectNodes, String tag) throws Exception
  {
    for (Node n : selectNodes)
    {
      if (tag.equals(RailComponent.RAILWAY_SEGMENTS))
      {
        List<Attribute> list = Validator.validateNodeAttributes(n);
        createSegment(n, list);
      }
      else if (tag.equals(RailComponent.RAILWAY_SIGNALS))
        createSignal(n);
      else if (tag.equals(RailComponent.RAILWAY_TRAINS)) createTrain(n);
    }
  }

  @SuppressWarnings("unchecked")
  private void createSegment(Node n, List<Attribute> list) throws Exception
  {
    List<Sensor> sensors = new LinkedList<Sensor>();
    String id = list.get(0).getValue();
    RailComponent component = new Segment(id);
    Segment segment = (Segment) component;
    Point segmentPosition = new Point();
    segment.setLabelPosition(segmentPosition);
    int[] segmentRouteKeys = null;

    for (Attribute attribute : list)
    {
      String aname = attribute.getName();
      String avalue = attribute.getValue();

      if (aname.equalsIgnoreCase("switchSegment") && avalue.equalsIgnoreCase("false"))
      {
        segment.setSwitchSegment(false);
        String[] position = n.selectSingleNode("Position").getText().split(",");
        segment.setPosition(new Point(Integer.parseInt(position[0]), Integer.parseInt(position[1])));
        String[] size = n.selectSingleNode("Size").getText().split(",");
        segment.setSize(new Point(Integer.parseInt(size[0]), Integer.parseInt(size[1])));
        List<Node> sensorNodes = n.selectNodes("Sensor");
        for (Node sensorNode : sensorNodes)
        {
          Attribute attrib = Validator.validateIdentifictorAttributes(sensorNode);
          Sensor sensor = new Sensor(attrib.getValue());
          List<Node> positionsList = sensorNode.selectNodes("Position");
          Point[] positionPoints = new Point[positionsList.size()];
          int i = 0;
          for (Node n2 : positionsList)
          {
            String[] positions = n2.getText().split(",");
            positionPoints[i] = new Point(Integer.parseInt(positions[0]), Integer.parseInt(positions[1]));
            i++;
          }
          sensor.setPosition(positionPoints);
          Node typeNode = sensorNode.selectSingleNode("Type");
          SensorType sensorType = Validator.validateSensorType(typeNode.getText().toUpperCase());
          sensor.setType(sensorType);
          segment.getSensorList().add(sensor);
          sensors.add(sensor);
          this.railwayMap.addToIdMap(sensor.getSensorId(), sensor);
        }
      }
      else if (aname.equalsIgnoreCase("switchSegment") && avalue.equalsIgnoreCase("true"))
      {
        segment.setSwitchSegment(true);
        if (Arrays.asList(RailComponent.INITIAL_SWITCHES).contains(id)) segment.setSwitchEnabled(true);
        List<Node> switchSegmentList = n.selectNodes("Position");
        Point[] positionPoints = new Point[switchSegmentList.size()];
        int i = 0;
        for (Node node : switchSegmentList)
        {
          String[] position = node.getText().split(",");
          positionPoints[i] = new Point(Integer.parseInt(position[0]), Integer.parseInt(position[1]));
          i++;
        }
        segment.setSwitchCoordinates(positionPoints);
      }
      else if (aname.equalsIgnoreCase("x") && !"".equals(avalue))
        segmentPosition.x = Integer.parseInt(avalue);
      else if (aname.equalsIgnoreCase("y") && !"".equals(avalue))
        segmentPosition.y = Integer.parseInt(avalue);
      else if (aname.equalsIgnoreCase("route"))
      {
        String[] routes = avalue.split(",");
        segmentRouteKeys = new int[routes.length];
        for (int i = 0; i < routes.length; i++)
          segmentRouteKeys[i] = Integer.parseInt(routes[i]);
      }
    }
    this.railwayMap.addToPaintList(component);
    for (int i = 0; i < segmentRouteKeys.length; i++)
    {
      this.railwayMap.addToRouteMap(segmentRouteKeys[i], component);
      for (Sensor s : sensors)
        this.railwayMap.addToRouteMap(segmentRouteKeys[i], s);
    }

    if (segment.isSwitchSegment())
    {
      for (int i = 0; i < segmentRouteKeys.length; i++)
      {
        if (Arrays.asList(RailComponent.SWITCH_UNIT1).contains(segment.getSegmentId()))
        {
          List<Segment> l = unitMap.get(1);
          if (l == null)
          {
            l = new ArrayList<Segment>();
            l.add(segment);
            unitMap.put(1, l);
          }
          else if (!l.contains(segment)) l.add(segment);
        }
        else if (Arrays.asList(RailComponent.SWITCH_UNIT2).contains(segment.getSegmentId()))
        {
          List<Segment> l = unitMap.get(2);
          if (l == null)
          {
            l = new ArrayList<Segment>();
            l.add(segment);
            unitMap.put(2, l);
          }
          else if (!l.contains(segment)) l.add(segment);
        }
        else if (Arrays.asList(RailComponent.SWITCH_UNIT3).contains(segment.getSegmentId()))
        {
          List<Segment> l = unitMap.get(3);
          if (l == null)
          {
            l = new ArrayList<Segment>();
            l.add(segment);
            unitMap.put(3, l);
          }
          else if (!l.contains(segment)) l.add(segment);
        }
        LinkedHashMap<Integer, List<Segment>> map = switchSegmentMap.get(segmentRouteKeys[i]);
        if (map == null) switchSegmentMap.put(segmentRouteKeys[i], unitMap);
      }
    }
    this.railwayMap.addToIdMap(((Segment) component).getSegmentId(), component);
  }

  private void createSignal(Node n) throws ParseException
  {
    List<Attribute> list = Validator.validateNodeAttributes(n);
    String signalId = "";
    Point p = new Point();
    int[] segmentRouteKeys = null;

    for (Attribute attribute : list)
    {
      String aname = attribute.getName();
      String avalue = attribute.getValue();

      if (aname.equals("id")) signalId = avalue;
      if (aname.equals("x")) p.x = Integer.parseInt(avalue);
      if (aname.equals("y")) p.y = Integer.parseInt(avalue);
      if (aname.equals("route"))
      {
        String[] routes = avalue.split(",");
        segmentRouteKeys = new int[routes.length];
        for (int i = 0; i < routes.length; i++)
          segmentRouteKeys[i] = Integer.parseInt(routes[i]);
      }
    }
    Signal signal = new Signal(p, signalId);
    for (int i = 0; i < segmentRouteKeys.length; i++)
      this.railwayMap.addToRouteMap(segmentRouteKeys[i], signal);
    this.railwayMap.addToPaintList(signal);
    this.railwayMap.addToIdMap(signalId, signal);
  }

  private void createTrain(Node n) throws ParseException
  {
    String trainId = "";
    Point position = new Point();
    Point trainLabelposition = new Point();
    Point size = new Point();
    int velocity = 0, deadline = 0;

    Attribute attrib = Validator.validateIdentifictorAttributes(n);
    trainId = attrib.getValue();
    String[] pos = n.selectSingleNode("Position").getText().split(",");
    position.x = Integer.parseInt(pos[0]);
    position.y = Integer.parseInt(pos[1]);
    trainLabelposition.x = position.x - 5;
    trainLabelposition.y = position.y - 5;

    String[] s = n.selectSingleNode("Size").getText().split(",");
    size.x = Integer.parseInt(s[0]);
    size.y = Integer.parseInt(s[1]);

    String name = n.selectSingleNode("Name").getText();

    try
    {
      velocity = Integer.parseInt(n.selectSingleNode("Velocity").getText());
    }
    catch (NumberFormatException nfe)
    {
      System.err.println(nfe.getMessage());
      return;
    }

    Train train = new Train(trainId, name, position, size, velocity, deadline);
    train.setLabelPosition(trainLabelposition);
    this.railwayMap.addTrain(train);
    this.railwayMap.addToPaintList(train);
    this.railwayMap.addToIdMap(trainId, train);
  }
}
